#include <algorithm/analytical_model/io/json/converter/shared/data/topology/geometry/Geometry.h>
#include <algorithm/analytical_model/io/json/transform/TransformData.h>
#include <algorithm/analytical_model/io/json/transform/TransformJSON.h>
#include <utils/HasValue.h>

#include <algorithm>
#include <array>
#include <cstddef>
#include <execution>
#include <string_view>

using namespace solar;
using namespace std::literals;

#define PP_CONCAT_IMPL(A, B) A##B
#define PP_CONCAT(A, B) PP_CONCAT_IMPL(A, B)

#define COMMON_CHECK_INPUT(input)                                                                  \
    const auto* value_ptr = (input).if_object();                                                   \
    if (value_ptr == nullptr)                                                                      \
    {                                                                                              \
        return;                                                                                    \
    }

#define COMMON_CHECK_OUTPUT(output)                                                                \
    auto* PP_CONCAT(output, _new_value_ptr) = (output).if_object();                                \
    if (PP_CONCAT(output, _new_value_ptr) == nullptr)                                              \
    {                                                                                              \
        (output).emplace_object();                                                                 \
        PP_CONCAT(output, _new_value_ptr) = (output).if_object();                                  \
    }

namespace solar::io::json::internal
{
    constexpr std::array heliostat_json_attributes = {
        "gap"sv, "subhelio_row_col"sv, "has_brace"sv, "geometry"sv, "thickness"sv, "position"sv};
    constexpr std::array receiver_json_attributes = {"geometry"sv, "position"sv, "normal"sv};
    constexpr std::array heliostat_para_json_attributes = {
        "reflectivity"sv,      "sigma_tracking"sv,       "sigma"sv,
        "sigma_slope_error"sv, "heliostat_glass_para"sv, "slope"sv,
        "refractivity"sv,      "glass_thickness"sv,      "glass_refractive_index"sv};

    static void transformGeometry(const TransformTag& /*unused*/, boost::json::value& value,
                                  const boost::json::value& extra_value)
    {
        if (const auto* geometry_ptr = value.get_object().if_contains("geometry");
            geometry_ptr != nullptr)
        {
            double thickness = kUndefined;
            if (const auto* thickness_ptr = extra_value.get_object().if_contains("thickness");
                thickness_ptr != nullptr)
            {
                thickness = thickness_ptr->to_number<double>();
            }
            auto type =
                internal::convertGeometryType(geometry_ptr->get_object().at("type").get_string());
            switch (type)
            {
            case GeometryType::kGeometryRectangle:
            {
                boost::json::value new_rectangle3d_value;
                // std::cout << value << '\n';
                boost::json::value normal_value;
                if (const auto* normal_ptr = value.get_object().if_contains("normal");
                    normal_ptr != nullptr)
                {
                    normal_value = *normal_ptr;
                }
                transformRectangle(TransformTag(), *geometry_ptr, value.at("position"),
                                   normal_value, thickness, new_rectangle3d_value);
                value.get_object()["geometry"] = new_rectangle3d_value;
                break;
            }
            case GeometryType::kGeometryCylinder:
            {
                boost::json::value new_cylinder3d_value;
                transformCylinder(TransformTag(), *geometry_ptr, value.at("position"),
                                  new_cylinder3d_value);
                value.get_object()["geometry"] = new_cylinder3d_value;
                break;
            }
            }
        }
    }
} // namespace solar::io::json::internal

void solar::io::json::transformJSON(
    const io::json::DeCompressTag& /*unused*/, const boost::json::value& input_json,
    boost::json::value& common_para_value, boost::json::value& rectangle_grid_value,
    boost::json::value& scene_para_value, boost::json::value& simulation_para_value,
    boost::json::value& sun_value, std::vector<boost::json::value>& heliostat_values,
    std::vector<boost::json::value>& receiver_values,
    std::vector<boost::json::value>& heliostat_para_values)
{
    COMMON_CHECK_INPUT(input_json)
    COMMON_CHECK_OUTPUT(common_para_value)
    COMMON_CHECK_OUTPUT(rectangle_grid_value)
    COMMON_CHECK_OUTPUT(scene_para_value)
    COMMON_CHECK_OUTPUT(simulation_para_value)
    COMMON_CHECK_OUTPUT(sun_value)
    heliostat_values.clear();
    receiver_values.clear();
    heliostat_para_values.clear();
    if (const auto* simulation_value_ptr = value_ptr->if_contains("simulation");
        simulation_value_ptr != nullptr)
    {
        transformSimulationPara(DeCompressTag(), *simulation_value_ptr, simulation_para_value);
        transformCommonPara(DeCompressTag(), *simulation_value_ptr, common_para_value);
        if (const auto* grid_ptr = simulation_value_ptr->as_object().if_contains("grid");
            grid_ptr != nullptr)
        {
            transformRectangleGrid(TransformTag(), *grid_ptr, rectangle_grid_value);
        }
    }
    boost::system::error_code error_code;
    if (const auto* const sun_value_ptr = input_json.find_pointer("/config/sun", error_code);
        error_code.value() == 0)
    {
        transformScenePara(DeCompressTag(), *sun_value_ptr, scene_para_value);
        sun_value = *sun_value_ptr;
    }
    if (const auto* const all_receivers_value_ptr =
            input_json.find_pointer("/field/receivers/group", error_code);
        error_code.value() == 0)
    {
        auto all_receivers_json_object = all_receivers_value_ptr->get_object();
        boost::json::value receiver_common_value;
        auto& receiver_common_value_object = receiver_common_value.emplace_object();
        for (const auto& attribute : internal::receiver_json_attributes)
        {
            if (const auto* current_value_ptr = all_receivers_json_object.if_contains(attribute);
                current_value_ptr != nullptr)
            {
                receiver_common_value_object.insert_or_assign(attribute, *current_value_ptr);
            }
        }
        if (const auto* receiver_differ_ptr =
                all_receivers_value_ptr->get_object().if_contains("differ");
            receiver_differ_ptr != nullptr)
        {
            auto receiver_differ_json_array = receiver_differ_ptr->get_array();
            receiver_values.resize(receiver_differ_json_array.size());
            std::transform(
                std::execution::par, receiver_differ_json_array.begin(),
                receiver_differ_json_array.end(), receiver_values.begin(),
                [&](const boost::json::value& receiver_differ)
                {
                    boost::json::value current_receiver_value;
                    auto& current_receiver_object = current_receiver_value.emplace_object();
                    for (const auto& attribute : receiver_common_value_object)
                    {
                        current_receiver_object.insert_or_assign(attribute.key(),
                                                                 attribute.value());
                    }
                    for (const auto& attribute : internal::receiver_json_attributes)
                    {
                        if (const auto* current_value_ptr =
                                receiver_differ.get_object().if_contains(attribute);
                            current_value_ptr != nullptr)
                        {
                            current_receiver_object.insert_or_assign(attribute, *current_value_ptr);
                        }
                    }
                    return current_receiver_value;
                });
        }
        else
        {
            receiver_values.emplace_back(receiver_common_value);
        }
        std::for_each(std::execution::par_unseq, receiver_values.begin(), receiver_values.end(),
                      [](boost::json::value& value)
                      {
                          boost::json::value extra_value;
                          extra_value.emplace_object();
                          internal::transformGeometry(TransformTag(), value, extra_value);
                      });
    }
    if (const auto* const all_heliostats_value_ptr =
            input_json.find_pointer("/field/heliostats/group", error_code);
        error_code.value() == 0)
    {
        auto all_heliostats_json_object = all_heliostats_value_ptr->get_object();
        boost::json::value heliostat_common_value;
        auto& heliostat_common_value_object = heliostat_common_value.emplace_object();
        for (const auto& attribute : internal::heliostat_json_attributes)
        {
            if (const auto* current_value_ptr = all_heliostats_json_object.if_contains(attribute);
                current_value_ptr != nullptr)
            {
                heliostat_common_value_object.insert_or_assign(attribute, *current_value_ptr);
            }
        }
        if (const auto* heliostat_differ_ptr =
                all_heliostats_value_ptr->get_object().if_contains("differ");
            heliostat_differ_ptr != nullptr)
        {
            auto heliostat_differ_json_array = heliostat_differ_ptr->get_array();
            heliostat_values.resize(heliostat_differ_json_array.size());
            std::transform(std::execution::par, heliostat_differ_json_array.begin(),
                           heliostat_differ_json_array.end(), heliostat_values.begin(),
                           [&](const boost::json::value& heliostat_differ)
                           {
                               boost::json::value current_heliostat_value;
                               auto& current_heliostat_object =
                                   current_heliostat_value.emplace_object();
                               for (const auto& attribute : heliostat_common_value_object)
                               {
                                   current_heliostat_object.insert_or_assign(attribute.key(),
                                                                             attribute.value());
                               }
                               for (const auto& attribute : internal::heliostat_json_attributes)
                               {
                                   if (const auto* current_value_ptr =
                                           heliostat_differ.get_object().if_contains(attribute);
                                       current_value_ptr != nullptr)
                                   {
                                       current_heliostat_object.insert_or_assign(
                                           attribute, *current_value_ptr);
                                   }
                               }
                               return current_heliostat_value;
                           });
        }
        else
        {
            heliostat_values.emplace_back(heliostat_common_value);
        }
        std::for_each(
            std::execution::par_unseq, heliostat_values.begin(), heliostat_values.end(),
            [](boost::json::value& value)
            {
                boost::json::value new_value;
                boost::json::value extra_value;
                extra_value.emplace_object();
                auto object = value.get_object();
                if (object.empty())
                {
                    return;
                }
                double thickness = kUndefined;
                if (const auto* thickness_ptr = object.if_contains("thickness");
                    thickness_ptr != nullptr)
                {
                    extra_value.get_object()["thickness"] = thickness_ptr->to_number<double>();
                    thickness = thickness_ptr->to_number<double>();
                }
                transformHeliostat(DeCompressTag(), value.at("gap"), value.at("subhelio_row_col"),
                                   thickness, value.at("has_brace").get_bool(), new_value);
                if (const auto* position_ptr = object.if_contains("position");
                    position_ptr != nullptr)
                {
                    new_value.get_object().insert_or_assign("position", *position_ptr);
                }
                if (const auto* geometry_ptr = object.if_contains("geometry");
                    geometry_ptr != nullptr)
                {
                    new_value.get_object().insert_or_assign("geometry", *geometry_ptr);
                    internal::transformGeometry(TransformTag(), new_value, extra_value);
                }

                value = new_value;
            });
    }
    if (const auto* const all_heliostats_value_ptr =
            input_json.find_pointer("/field/heliostats/group", error_code);
        error_code.value() == 0)
    {
        boost::json::object all_heliostats_para_json_object;
        if (const auto* material_ptr =
                all_heliostats_value_ptr->get_object().if_contains("material");
            material_ptr != nullptr)
        {
            all_heliostats_para_json_object = material_ptr->get_object();
        }
        boost::json::value heliostat_para_common_value;
        auto& heliostat_para_common_value_object = heliostat_para_common_value.emplace_object();
        for (const auto& attribute : internal::heliostat_para_json_attributes)
        {
            if (const auto* current_value_ptr =
                    all_heliostats_para_json_object.if_contains(attribute);
                current_value_ptr != nullptr)
            {
                heliostat_para_common_value_object.insert_or_assign(attribute, *current_value_ptr);
            }
        }
        if (const auto* heliostat_differ_ptr =
                all_heliostats_value_ptr->get_object().if_contains("differ");
            heliostat_differ_ptr != nullptr)
        {
            auto heliostat_differ_json_array = heliostat_differ_ptr->get_array();
            heliostat_para_values.resize(heliostat_differ_json_array.size());
            std::transform(
                std::execution::par, heliostat_differ_json_array.begin(),
                heliostat_differ_json_array.end(), heliostat_para_values.begin(),
                [&](const boost::json::value& heliostat_differ)
                {
                    boost::json::value current_heliostat_para_value;
                    auto& current_heliostat_para_object =
                        current_heliostat_para_value.emplace_object();
                    for (const auto& attribute : heliostat_para_common_value_object)
                    {
                        current_heliostat_para_object.insert_or_assign(attribute.key(),
                                                                       attribute.value());
                    }
                    for (const auto& attribute : internal::heliostat_para_json_attributes)
                    {

                        if (const auto* current_material_value_ptr =
                                heliostat_differ.get_object().if_contains("material");
                            current_material_value_ptr != nullptr)
                        {
                            if (const auto* current_value_ptr =
                                    current_material_value_ptr->get_object().if_contains(attribute);
                                current_value_ptr != nullptr)
                            {
                                current_heliostat_para_object.insert_or_assign(attribute,
                                                                               *current_value_ptr);
                            }
                        }
                    }
                    return current_heliostat_para_value;
                });
        }
        else
        {
            heliostat_para_values.emplace_back(heliostat_para_common_value);
        }
        std::for_each(std::execution::par_unseq, heliostat_para_values.begin(),
                      heliostat_para_values.end(),
                      [](boost::json::value& value)
                      {
                          boost::json::value new_value;
                          transformHeliostatPara(DeCompressTag(), value, new_value);
                          value = new_value;
                      });
    }
}
